#include "node.h"
#include "CaffProc/processor.h"
#include "CaffCache/hash_table.h"
#include "CaffMC/memory.h"
#include "mreq.h"
#include "sim.h"
#include "types.h"

extern Simulator Sim;

using namespace manifold::kernel;

Node::Node (int nodeID)
{
    this->has_mc= false;
    this->nodeID = nodeID;
}

Node::~Node ()
{
    map<module_t, Module*>::iterator it;

    for (it = mod.begin (); it != mod.end (); it++)
    {
        /** Mesh Interface freed by NOC.  */
        if (it->first == NI_M)
            continue;
        /** processor freed by Zesto.  */	
	if (it->first == PR_M)
            continue;
        delete (it->second);
    }
    mod.clear ();
}

void Node::build_manifold_node (CompId_t NI_cid)
{
    mod_cid[NI_M] = NI_cid;
    mod[NI_M] = (Module *) Component::GetComponent(mod_cid[NI_M]);

    //mod[PR_M] = new processor ((ModuleID){nodeID, PR_M});

    //TODO: Protocols should be passed as settings.


    mod_cid[L1_M] = Component::Create<Hash_table, Hash_table_settings> (0, (Hash_table_settings) {nodeID, L1_M, "L1",
                                Sim.settings.l1_cache_type,
                                MOESI_PRO, /** Client protocol.  */
                                CACHE_PRO, /** Directory protocol.  */
                                TIER0, HASH_NODE_STRIDE,
                                Sim.settings.l1_cache_size,
                                Sim.settings.l1_cache_assoc,
                                Sim.settings.cache_line_size,
                                Sim.settings.l1_mshrs,
                                Sim.settings.l1_infinite,
                                Sim.settings.l1_hit_time,
                                Sim.settings.l1_lookup_time});
    mod[L1_M] = (Module *) Component::GetComponent(mod_cid[L1_M]);
    Clock::Register<Hash_table> ((Hash_table*)mod[L1_M], &Hash_table::tick, &Hash_table::tock);
    

    mod_cid[L2_M] = Component::Create<Hash_table, Hash_table_settings> (0, (Hash_table_settings) {nodeID, L2_M, "L2",
                                Sim.settings.l2_cache_type,
                                MEM_PRO, /** Client protocol.  */
                                MOESI_PRO, /** Directory protocol.  */
                                TIER1, HASH_MC, /** Goes to this local node.  */
                                Sim.settings.l2_cache_size,
                                Sim.settings.l2_cache_assoc,
                                Sim.settings.cache_line_size,
                                Sim.settings.l2_mshrs,
                                Sim.settings.l2_infinite,
                                Sim.settings.l2_hit_time,
                                Sim.settings.l2_lookup_time});
    mod[L2_M] = (Module *) Component::GetComponent(mod_cid[L2_M]);
    Clock::Register<Hash_table> ((Hash_table*)mod[L2_M], &Hash_table::tick, &Hash_table::tock);

    /** Register component statistics engines with the stat manager.  */
    //Sim.stat_manager->add_processor_engine(nodeID, ((processor *) mod[PR_M])->stats);
    Sim.stat_manager->add_L1_engine(nodeID, ((Hash_table *) mod[L1_M])->stats);
    Sim.stat_manager->add_L2_engine(nodeID, ((Hash_table *) mod[L2_M])->stats);
//    Sim.stat_manager->add_L3_engine(nodeID, ((Hash_table *) mod[L3_M])->stats);
//    Sim.stat_manager->add_mesh_interface_stat_engine(nodeID, ((Bus_interface *) mod[NI_M])->stats);

}

void Node::manifold_connect ()
{

    /** Manifold Connect*/
    /** format, src_cid, src_mid_outport_id, dest_cid, dest_mid_inport_id, function, delay */
    
    //processor
    Manifold::Connect (mod_cid[PR_M], L1_M, mod_cid[L1_M], PR_M, &Module::manifold_enque_now, 1);

    //L1
    Manifold::Connect (mod_cid[L1_M], PR_M, mod_cid[PR_M], L1_M, &Module::manifold_enque_now, Sim.settings.l1_hit_time);
    Manifold::Connect (mod_cid[L1_M], L2_M, mod_cid[L2_M], L1_M, &Module::manifold_enque_now, Sim.settings.l1_lookup_time);
    Manifold::Connect (mod_cid[L1_M], NI_M, mod_cid[NI_M], L1_M, &Module::manifold_enque_now, Sim.settings.l1_lookup_time);

    //L2
    Manifold::Connect (mod_cid[L2_M], L1_M, mod_cid[L1_M], L2_M, &Module::manifold_enque_now, Sim.settings.l2_hit_time);
    Manifold::Connect (mod_cid[L2_M], NI_M, mod_cid[NI_M], L2_M, &Module::manifold_enque_now, Sim.settings.l2_lookup_time);
    if ( has_mc )
        Manifold::Connect (mod_cid[L2_M], MC_M, mod_cid[MC_M], L2_M, &Module::manifold_enque_now, Sim.settings.l2_lookup_time);

    //NI
    Manifold::Connect (mod_cid[NI_M], L1_M, mod_cid[L1_M], NI_M, &Module::manifold_enque_now, 10);
    Manifold::Connect (mod_cid[NI_M], L2_M, mod_cid[L2_M], NI_M, &Module::manifold_enque_now, 10);
    if ( has_mc )
        Manifold::Connect (mod_cid[NI_M], MC_M, mod_cid[MC_M], NI_M, &Module::manifold_enque_now, 10);

    if ( has_mc )
    {
        //MC
        Manifold::Connect (mod_cid[MC_M], L2_M, mod_cid[L2_M], MC_M, &Module::manifold_enque_now, MEM_HIT_TIME);
        Manifold::Connect (mod_cid[MC_M], NI_M, mod_cid[NI_M], MC_M, &Module::manifold_enque_now, MEM_HIT_TIME);
    }
}

void Node::add_MC_to_manifold_node ()
{
    has_mc = true;
    mod_cid[MC_M] = Component::Create<Memory_controller, ModuleID, int> (0, (ModuleID){nodeID,MC_M}, MEM_HIT_TIME);
    mod[MC_M] = (Module *) Component::GetComponent(mod_cid[MC_M]);
    Clock::Register<Memory_controller> ((Memory_controller*)mod[MC_M], &Memory_controller::tick, &Memory_controller::tock);

    if (GENERAL_DEBUG)
        fprintf (stderr, "New MC (%d)\n", nodeID);

    /** Register component statistics engines with the stat manager.  */
    Sim.stat_manager->add_memory_controller_engine (nodeID, ((Memory_controller *) mod[MC_M])->stats);
}

void Node::tick (void)
{
    std::map<module_t, Module*>::iterator it;
    for (it = mod.begin(); it!= mod.end(); it++)
    {
        /** Processors never should tick here, they tick in sim.cpp.  */
        if((*it).first != PR_M && (*it).second->tick_tock_p ())
            (*it).second->tick();
    }
}

void Node::tock (void)
{
    std::map<module_t, Module*>::iterator it;
    for (it = mod.begin(); it!= mod.end(); it++)
    {
        /** Only processors and NI have a functional tock, all else will fatal.  */
#if SIM_WITH_ZESTO
        if (((*it).first == NI_M) && (*it).second->tick_tock_p ())
            (*it).second->tock();
#else 
        if (((*it).first == PR_M || (*it).first == NI_M) && (*it).second->tick_tock_p ())
            (*it).second->tock();

#endif
    }
}
